/*
 * Copyright (c) 2007, Olof Naessen and Per Larsson
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, 
 * are permitted provided that the following conditions are met:
 *
 *    * Redistributions of source code must retain the above copyright notice, 
 *      this list of conditions and the following disclaimer.
 *    * Redistributions in binary form must reproduce the above copyright notice, 
 *      this list of conditions and the following disclaimer in the documentation 
 *      and/or other materials provided with the distribution.
 *    * Neither the name of the Darkbits nor the names of its contributors may be 
 *      used to endorse or promote products derived from this software without 
 *      specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <string>
#include <allegro.h>
#include <iostream>

#include "ball.hpp"
#include "levelhandler.hpp"
#include "resourcehandler.hpp"

const int JUMP_STRENGTH = 13;
const int METAL_JUMP_STRENGTH = 7;
const int MAX_AIR_SPEED = 7;
const int MAX_WATER_SPEED = 4;
const int AIR_CONTROL = 4; // Lower = more control

const int STARS_TO_GET_METAL = 52;
const int METALBALL_COOLDOWN = 400;
const int METALBALL_TIME = 300;

const int STARS_TO_GET_RUSH = 17;
const int RUSH_POWER = 10;

Ball::Ball(int x, int y) {
	
    sprite = ResourceHandler::getInstance()->getBitmap("ball.bmp");
	spriteBlink = ResourceHandler::getInstance()->getBitmap("ballblink.bmp");
    spriteIron = ResourceHandler::getInstance()->getBitmap("balliron.bmp");
	spriteBlinkIron = ResourceHandler::getInstance()->getBitmap("ballblinkiron.bmp");
    spriteCoolDown = ResourceHandler::getInstance()->getBitmap("ballcooldown.bmp");
    spriteBlinkCoolDown = ResourceHandler::getInstance()->getBitmap("ballcooldown.bmp");

	bounceSample = ResourceHandler::getInstance()->getSample("bounce.wav");
	rushSample = ResourceHandler::getInstance()->getSample("rush.wav");
	jumpSample = ResourceHandler::getInstance()->getSample("jump.wav");

	this->x = x;
	this->y = y;
	dy = 0;
	dx = 2;
	setState(AIR_DOWN);
	lastKeyUp = false;
	bouncing = false;
	metalBallCounter = 0;
	speedCooldown = 0;
    blinkCounter = 0;
	freezeTime = 0;
    mImortal = false;
}

void Ball::logic(TileMap *tileMap) {

    if (key[KEY_F1])
    {
        mImortal = true;
    }

    if (key[KEY_F2])
    {
        mImortal = false;
    }

	if (state == DEAD) {
		return;
	}

	if (freezeTime > 0) {
		freezeTime--;
		return;
	}

	blinkCounter++;

	if (dx < -2) {
		dx++;
		dy = 0;
	}

	if (dx > 2) {
		dx--;
		dy = 0;
	}

	if (metalBallCounter < 0) {
		metalBallCounter++;
	}

	if (metalBallCounter > 0) {
		metalBallCounter--;
		if (metalBallCounter == 0) {
			metalBallCounter = -METALBALL_COOLDOWN;
		}
	}	

	if ((key[KEY_LSHIFT] || key[KEY_RSHIFT]) && metalBallCounter == 0 && LevelHandler::getInstance()->getStarsCollected() >= STARS_TO_GET_METAL) {
		metalBallCounter = METALBALL_TIME;
		play_sample(rushSample, 150, 128, 400, 0);
	}


	bool keyUp = key[KEY_UP];

	if (tileMap->getTileAtPixel(getCenterX(), getCenterY())->isWater() && !isMetal()) {
		logicWater(tileMap, keyUp);
		lastKeyUp = keyUp;
		upHeldDuringJump = keyUp;
		return;
	}

	bool keyCtrl = key[KEY_LCONTROL] || key[KEY_RCONTROL];
	bool canRush = LevelHandler::getInstance()->getStarsCollected() >= STARS_TO_GET_RUSH;

	// Rushing
	if (keyCtrl && !lastKeyCtrl && dx < 0 && speedCooldown == 0 && canRush && state != ROLL && !bouncing) {
		dx = -RUSH_POWER;
		dy = 0;
		speedCooldown = 1;
		play_sample(rushSample, 100, 128, 1000, 0);
	}

	if (keyCtrl && !lastKeyCtrl && dx > 0 && speedCooldown == 0 && canRush && state != ROLL && !bouncing) {
		dx = RUSH_POWER;
		dy = 0;
		speedCooldown = 1;
		play_sample(rushSample, 100, 128, 1000, 0);
	}

	lastKeyCtrl = keyCtrl;

	// Interrupting jumping
	if (!keyUp && state == AIR_UP && dy > AIR_CONTROL) {
		dy = AIR_CONTROL;		
	}

	if (!keyUp && (state == AIR_UP || state == AIR_DOWN)) {
		upHeldDuringJump = false;
	}

	// Stop bouncing if fall of ledge
	if (y > bounceY) {
		bouncing = false;
	}

	// Jumping
	if (!lastKeyUp && keyUp && (state == ROLL || bouncing)) {
		dy = isMetal() ? METAL_JUMP_STRENGTH : JUMP_STRENGTH;
		setState(AIR_UP);
		upHeldDuringJump = true;
		// Align to tile
		if (bouncing) {
			y = bounceY;
		}
		play_sample(jumpSample, 100, 128, 1000, 0);
		bouncing = false;		
	}
	lastKeyUp = keyUp;

	// Sideways movement
	x += dx;

	// Left Bounce
	if (dx < 0 && (tileMap->getTileAtPixel(x, y)->isSolid() || tileMap->getTileAtPixel(x, y + 15)->isSolid())) {		
		// Align to tile
		x = x - (x % 16) + 16;
		// Bounce unless in narrow vertical well
		if (!(tileMap->getTileAtPixel(x + 16, y)->isSolid() || tileMap->getTileAtPixel(x + 16, y + 15)->isSolid())) {
			dx = -dx;
		}
	}

	// Right Bounce
	if (dx > 0 && (tileMap->getTileAtPixel(x + 15, y)->isSolid() || tileMap->getTileAtPixel(x + 15, y + 15)->isSolid())) {
		// Align to tile
		x = x - (x % 16);
		// Bounce unless in narrow vertical well
		if (!(tileMap->getTileAtPixel(x - 1, y)->isSolid() || tileMap->getTileAtPixel(x - 1, y + 15)->isSolid())) {
			dx = -dx;
		}
	}

	// Falling, raising, movement
	if (state == AIR_DOWN) {
		y += dy > MAX_AIR_SPEED ? MAX_AIR_SPEED : dy;
	} else if (state == AIR_UP) {
		y -= dy > MAX_AIR_SPEED ? MAX_AIR_SPEED : dy;
	}

	// Falling, acceleration
	if (state == AIR_DOWN) {
		dy += 1;
		if (dy > 1 && dy < 7 && tileMap->getTileAtPixel(getCenterX(), getCenterY() + 8)->isWater() && !isMetal()) {
			dy = 1;
		}
	}

	// Raising, deacceleration
	if (state == AIR_UP) {
		dy--;
		if (dy < 0) {
			setState(AIR_DOWN);
			dy = 0;
		}
	}

	// Ceiling bouncing
	if (state == AIR_UP && (tileMap->getTileAtPixel(x+2, y)->isSolid() || tileMap->getTileAtPixel(x + 13, y)->isSolid())) {
		setState(AIR_DOWN);
		// Align to tile
		y = y - (y % 16) + 16;
	}

	// Ground bouncing / landing
	bool onSolidGround = tileMap->getTileAtPixel(x + 2, y + 16)->isSolid() || tileMap->getTileAtPixel(x + 13, y + 16)->isSolid();
	if (state == AIR_DOWN && onSolidGround) {
		if (keyUp && !upHeldDuringJump) {
			// Rejump
			dy = isMetal() ? METAL_JUMP_STRENGTH : JUMP_STRENGTH;
			setState(AIR_UP);
			upHeldDuringJump = true;
			play_sample(jumpSample, 100, 128, 1000, 0);
		} else {
			if (dy > AIR_CONTROL && !isMetal()) {
				// Bounce
				dy = dy < (AIR_CONTROL * 2) ? dy / 2 : AIR_CONTROL;
				setState(AIR_UP);
				bouncing = true;
			} 
			else {
				// Land
				setState(ROLL);
				dy = 0;
				bouncing = false;				
			}
			play_sample(bounceSample, 80, 128, 1000, 0);
		}
		// Align to tile
		y = y - (y % 16);
		bounceY = y;
		speedCooldown = 0;		
	}

	// Falling from rolling
	if (state == ROLL && !onSolidGround) {
		setState(AIR_DOWN);
		dy = 0;
	}

	// Fall off screen
	if (y > 380) {
		kill();
	}
}

void Ball::logicWater(TileMap *tileMap, bool keyUp) {
/*
	// Interrupting jumping
	if (!keyUp && state == AIR_UP && dy > AIR_CONTROL) {
		dy = AIR_CONTROL;		
	}

	if (!keyUp && (state == AIR_UP || state == AIR_DOWN)) {
		upHeldDuringJump = false;
	}
*/
	// Stop bouncing if fall of ledge
	if (y > bounceY) {
		bouncing = false;
	}
/*
	// Jumping
	if (!lastKeyUp && keyUp && (state == ROLL || bouncing)) {
		dy = JUMP_STRENGTH;
		setState(AIR_UP);
		upHeldDuringJump = true;
		// Align to tile
		if (bouncing) {
			y = bounceY;
		}
		bouncing = false;
	}
	lastKeyUp = keyUp;
*/

	// Sideways movement
	x += dx;

	// Left Bounce
	if (dx < 0 && (tileMap->getTileAtPixel(x, y)->isSolid() || tileMap->getTileAtPixel(x, y + 15)->isSolid())) {		
		// Align to tile
		x = x - (x % 16) + 16;
		// Bounce unless in narrow vertical well
		if (!(tileMap->getTileAtPixel(x + 16, y)->isSolid() || tileMap->getTileAtPixel(x + 16, y + 15)->isSolid())) {
			dx = -dx;
		}
	}

	// Right Bounce
	if (dx > 0 && (tileMap->getTileAtPixel(x + 15, y)->isSolid() || tileMap->getTileAtPixel(x + 15, y + 15)->isSolid())) {
		// Align to tile
		x = x - (x % 16);
		// Bounce unless in narrow vertical well
		if (!(tileMap->getTileAtPixel(x - 1, y)->isSolid() || tileMap->getTileAtPixel(x - 1, y + 15)->isSolid())) {
			dx = -dx;
		}
	}

	// Falling, raising, movement
	if (state == AIR_DOWN) {
		y += dy > MAX_WATER_SPEED ? MAX_WATER_SPEED : dy;
	} else if (state == AIR_UP) {
		y -= dy > MAX_WATER_SPEED ? MAX_WATER_SPEED : dy;
	}


	// Floating, acceleration
	if (state == AIR_UP) {
		dy += 1;
		if (keyUp) {
			if (dy < 4) {
				dy = 4;
			}
			if (dy > 16) {
				dy = 16;
			}
		} else {
			if (dy > 1) {
				dy = 1;
			}
		}
	}

	// Falling, deacceleration
	if (state == AIR_DOWN) {
		if (keyUp) {
			dy-=2;
		} else {
			dy--;
		}
		if (dy < 0) {
			setState(AIR_UP);
			dy = 0;
		}
	}

	// Ground bouncing
	if (state == AIR_DOWN && (tileMap->getTileAtPixel(x+2, y+16)->isSolid() || tileMap->getTileAtPixel(x + 13, y+16)->isSolid())) {
		setState(AIR_UP);
		// Align to tile
		y = y - (y % 16);
	}

	// Ceiling bouncing / landing
	bool onSolidGround = tileMap->getTileAtPixel(x + 2, y)->isSolid() || tileMap->getTileAtPixel(x + 13, y)->isSolid();
	if (state == AIR_UP && onSolidGround) {
		/*if (keyUp && !upHeldDuringJump) {
			// Rejump
			dy = JUMP_STRENGTH;
			setState(AIR_UP);
			upHeldDuringJump = true;
		} else */{
			if (dy > AIR_CONTROL) {
				// Bounce
				dy = dy < (AIR_CONTROL * 2) ? dy / 2 : AIR_CONTROL;
				setState(AIR_DOWN);
				bouncing = true;
			} 
			else {
				// Land
				setState(ROLL);
				dy = 0;
				bouncing = false;
			}
		}
		// Align to tile
		y = y - (y % 16) + 16;
		bounceY = y;
	}

	// Falling from rolling
	if (state == ROLL && !onSolidGround) {
		setState(AIR_UP);
		dy = 0;
	}
}

void Ball::draw(BITMAP *dest, int scroll) {
	if (dx > 2 || dx < -2) {
		int px;
		for (px = 0; px < 8; px++) {
			if ((blinkCounter + px * 2) % 4 == 0) {
				drawBall(dest, x - (dx * px) / 2, y, scroll);
			}
		}
	}
	drawBall(dest, x, y, scroll);
}

void Ball::drawBall(BITMAP *dest, int x, int y, int scroll) {
	
    int frame = (x / 8) % (sprite->w / 16);
    int frame2 = (x / 8)% 10;
    
    masked_blit(ResourceHandler::getInstance()->getBitmap("dante-running3.bmp"),
                dest,
                frame2 * 48, 0, x - scroll, y - 32, 48, 48);

    if (blinkCounter % 300 >= 260)
    {
        if (isMetal())
        {
            masked_blit(spriteBlinkIron, dest, frame * 16, 0, x - scroll, y, 16, 16);
        }
        else if (metalBallCounter < 0) 
        {
            if (blinkCounter % 4 < 2)
            {
                masked_blit(spriteBlinkCoolDown, dest, frame * 16, 0, x - scroll, y, 16, 16);
            }
            else
            {
                 masked_blit(sprite, dest, frame * 16, 0, x - scroll, y, 16, 16);
            }
        }
        else
        {
             masked_blit(spriteBlink, dest, frame * 16, 0, x - scroll, y, 16, 16);
        }
    }
    else
    {
        if (isMetal())
        {
            masked_blit(spriteIron, dest, frame * 16, 0, x - scroll, y, 16, 16);
        }
        else if (metalBallCounter < 0) 
        {
            if (blinkCounter % 4 < 2)
            {
                masked_blit(spriteCoolDown, dest, frame * 16, 0, x - scroll, y, 16, 16);
            }
            else
            {
                 masked_blit(sprite, dest, frame * 16, 0, x - scroll, y, 16, 16);
            }
        }
        else
        {
            masked_blit(sprite, dest, frame * 16, 0, x - scroll, y, 16, 16);    
        }
    }
}

int Ball::getCenterX() {
	return x + 8;
}

int Ball::getCenterY() {
	return y + 8;
}

void Ball::setState(State newState) {
	state = newState;
}

Ball::State Ball::getState() {
	return state;
}

void Ball::setPosition(int x, int y) {
	this->x = x;
	this->y = y;
	bouncing = false;
	dy = 0;
	setState(AIR_DOWN);
}

int Ball::getMetalBarPosition() {
	if (metalBallCounter > 0) {
		return 100 - (metalBallCounter * 100) / METALBALL_TIME;
	} else {
		return -(metalBallCounter * 100) / METALBALL_COOLDOWN;
	}
}

void Ball::kill() {
    if (!mImortal)
    {
	    setState(DEAD);
    }
}

bool Ball::isMetal() {
	return metalBallCounter > 0;
}

void Ball::freeze(int time) {
	 freezeTime = time;
}
